﻿#if DatabaseTests

using System;
using System.Linq;
using NUnit.Framework;
using OpenWaves.ActivityFeed.UnitTests.Web;

#if DatabaseTests_NHibernate
using NHibernate;
using OpenWaves.ActivityFeed.NHibernate;
using OpenWaves.ActivityFeed.UnitTests.Web;
using OpenWaves.ActivityFeed.UnitTests_NHibernate;

#endif

#if DatabaseTests_RavenDB
using OpenWaves.ActivityFeed.RavenDB;
using OpenWaves.ActivityFeed.UnitTests.Web;

#endif

namespace OpenWaves.ActivityFeed.UnitTests
{
    public abstract class ActivityFeedServiceTests : ActivityFeedServiceTestsBase
    {
        [Test]
        public void Given_new_subscriber_When_subscribe_to_topics_Then_GetSubscriptions_returns_topics()
        {
            var subscriberId = "1";
            var topics = new[] { new Topic("topic 1"), new Topic("topic 2") };

            When(service =>
                     {
                         service.Subscribe(subscriberId, topics);
                     });

            Then(service =>
                     {
                         Assert.That(service.GetSubscriber(subscriberId).Subscriptions, Is.EquivalentTo(topics));
                     });
        }

        [Test]
        public void Given_new_subscriber_After_multiple_subscriptions_GetSubscriptions_returns_case_insensitive_union_of_topics()
        {
            var subscriberId = "1";
            var topics = new[] { new Topic("topic 1"), new Topic("topic 2") };
            var topics2 = new[] { new Topic("TOPIC 1"), new Topic("topic 3") };

            When(service =>
                     {
                         service.Subscribe(subscriberId, topics);
                         service.Subscribe(subscriberId, topics2);
                     });

            Then(service =>
                     {

                         Assert.That(service.GetSubscriber(subscriberId).Subscriptions, Is.EquivalentTo(new[] { new Topic("topic 1"), new Topic("topic 2"), new Topic("topic 3") }));
                     });
        }

        [Test]
        public void Given_2_subscribers_Subscriptions_are_specific_to_subscriber()
        {
            var subscriberId = "1";
            var subscriberId2 = "2";
            var topics = new[] { new Topic("topic 1"), new Topic("topic 2") };

            When(service =>
            {
                service.Subscribe(subscriberId, topics);
            });

            Then(service =>
            {

                Assert.That(service.GetSubscriber(subscriberId2).Subscriptions, Is.Empty);
            });
        }

        [Test]
        public void Given_existing_subscriber_following_topics_When_Unsubscribed_from_topic_Then_GetSubscriptions_does_not_return_topic()
        {
            var subscriberId = "1";
            var topics = new[] { new Topic("topic 1"), new Topic("topic 2") };

            Given(service =>
                     {

                         service.Subscribe(subscriberId, topics);
                     });

            When(service =>
            {

                service.Unsubscribe(subscriberId, new[] { new Topic("TOPIC 2") });
            });

            Then(service =>
            {

                Assert.That(service.GetSubscriber(subscriberId).Subscriptions, Is.EquivalentTo(new[] { new Topic("topic 1") }));
            });

        }

        // PublishPublicUpdate
        [Test]
        public void When_event_is_published_Then_updateDto_is_returned()
        {
            Update update = null;
            When(service =>
            {

                update = service.PublishUpdate(new DummyUpdateContent(), "1111", "source", Enumerable.Empty<Topic>(), true, Enumerable.Empty<UserGroup>());
            });

            Assert.That(!string.IsNullOrEmpty(update.Id));
        }

        [Test]
        public void Given_subscriber_follows_topic_When_event_on_topic_is_published_Then_GetEvents_for_subscriber_returns_event()
        {
            const string subscriberId = "1";
            Topic topic = new Topic("topic");
            var content = new DummyUpdateContent();

            Given(service =>
                      {

                          service.Subscribe(subscriberId, new[] { topic });
                      });

            When(service =>
                     {

                         service.PublishUpdate(content, "1234", "source", new[] { topic }, true, Enumerable.Empty<UserGroup>());
                     });

            Then(service =>
                     {

                         var subscriber = service.GetSubscriber(subscriberId);
                         var update = service.GetUpdates().ForSubscriber(subscriber).Single();
                         Assert.That(update.Content, Is.EqualTo(content));
                     });
        }

        [Test]
        public void Given_test_updates_Then_GetUpdates_updates_are_returned_in_valid_order()
        {
            Given(service =>
              {


                  using (var fakeTime = FakeTimeScope.Begin())
                  {
                      fakeTime.Now = new DateTime(2000, 1, 1);
                      service.PublishPublicUpdate(new Post("test"), "user1", "source", new Topic("publisher1"));
                      fakeTime.Now = new DateTime(2000, 1, 2);
                      service.PublishPublicUpdate(new Post("test2"), "user1", "source", new Topic("publisher1"));
                  }
              });

            Then(service =>
              {

                  var updates = service.GetUpdates().ToList();
                  Assert.That(((Post)updates[0].Content).Text, Is.EqualTo("test2"));
              });
        }

        [Test]
        public void When_user_publish_topics_Then_distinct_topics_list_is_added()
        {
            Update update = null;
            When(service =>
            {

                update = service.PublishPublicUpdate(new DummyUpdateContent(), "1234", "source", new Topic("111"), new Topic("111"), new Topic("222"), new Topic("111"));
            });

            Then(service =>
            {

                update = service.GetUpdate(update.Id);
                Assert.That(update.Topics.Count(t => "111" == t), Is.EqualTo(1));
            });
        }

        [Test]
        public void When_user_publishes_update_Then_distinct_userGroups_are_added()
        {
            var aGroup = new UserGroup("role1");

            Update update = null;
            When(service =>
            {

                update = service.PublishUpdate(new DummyUpdateContent(), "1234", "source",
                    new[] { new Topic("111"), new Topic("111"), new Topic("222"), new Topic("111") },
                    true, new[] { aGroup, new UserGroup("role2"), aGroup });
            });

            Then(service =>
            {

                update = service.GetUpdate(update.Id);
                Assert.That(update.UserGroups.Count(aGroup.Equals), Is.EqualTo(1));
            });
        }

        // GetUpdate
        [Test]
        public void Given_update_After_GetUpdate_Then_update_is_returned()
        {
            Update update = null;
            var content = new DummyUpdateContent();
            When(service =>
            {

                update = service.PublishPublicUpdate(content, "1234", "source");
            });

            Then(service =>
            {

                update = service.GetUpdate(update.Id);
                Assert.That(update.Content, Is.EqualTo(content));
            });
        }

        // RemoveUpdate
        [Test]
        public void Given_update_After_remove_update_Then_update_not_exists()
        {
            Update update = null;
            Topic topic = Topic.FromUserId("john");

            When(service =>
            {

                update = service.PublishPublicUpdate(new DummyUpdateContent(), "1234", "source", topic);
            });

            When(service =>
            {

                update = service.GetUpdate(update.Id);
                service.RemoveUpdate(update);
            });

            Then(service =>
            {

                Assert.That(service.GetUpdates().Count(), Is.EqualTo(0));
            });
        }


        // GetUpdates (for subscriber)
        [Test]
        public void When_event_is_published_Then_GetUpdates_returns_this_event_for_publisher()
        {
            var publisherId = "1";
            var content = new DummyUpdateContent();
            When(service =>
            {

                service.PublishPublicUpdate(content, publisherId, "source");
            });

            Then(service =>
            {

                var subscriber = service.GetSubscriber(publisherId);
                var update = service.GetUpdates().ForSubscriber(subscriber).Single();
                Assert.That(update.Content, Is.EqualTo(content));
            });
        }

        [Test]
        public void GetSubscribers_returns_all_subscribers_with_at_least_one_subscription()
        {
            const string firstPublisher = "1";
            const string anotherPublisher = "2";

            When(service =>
                     {

                         service.Subscribe(firstPublisher, new[] { new Topic("test1"), new Topic("test2") });
                         service.Subscribe(anotherPublisher,
                                           new[] { new Topic("test1"), new Topic("test2"), new Topic("test3") });
                     });

            When(service =>
                     {

                         service.Unsubscribe(firstPublisher, new[] { new Topic("test1"), new Topic("test2") });
                         service.Unsubscribe(anotherPublisher, new[] { new Topic("test1"), new Topic("test2") });
                     });

            Then(service =>
                     {

                         var subscribers = service.GetSubscribers().WithAtLeastOneSubscription().ToList();
                         Assert.IsNotEmpty(subscribers);
                         Assert.That(subscribers.First().Id, Is.EqualTo(anotherPublisher));
                     });
        }

        [Test]
        public void GetSubscribers_returns_subscribers_for_specified_ids()
        {
            const string firstPublisher = "1";
            const string anotherPublisher = "2";

            When(service =>
                {
                    service.Subscribe(firstPublisher, new[] { new Topic("topic1") });
                    service.Subscribe(anotherPublisher, new[] { new Topic("topic1") });
                });

            Then(service =>
                {
                    Assert.That(service.GetSubscribers().WithIds(new string[] { "1", "2", "3" }).Count(), Is.EqualTo(2));
                    Assert.IsEmpty(service.GetSubscribers().WithIds(new string[] { "4" }));
                });
        }        

        [Test]
        public void When_user_subscribe_to_topics_Then_GetSubscriber_returns_his_subscriptions()
        {
            const string firstPublisher = "1";

            Given(service =>
            {
                service.Subscribe(firstPublisher, new[] { new Topic("topic1") });
                service.Subscribe(firstPublisher, new[] { new Topic("topic2") });
            });

            When(service =>
                {
                    service.Unsubscribe(firstPublisher, new[] { new Topic("topic1") });
                });

            Then(service =>
                {
                    var subscriber = service.GetSubscriber(firstPublisher);
                    var subscriptions = subscriber.Subscriptions.ToList();

                    Assert.That(subscriptions.Count(), Is.EqualTo(1));
                });
        }

        [Test]
        public void When_users_subscribe_to_a_topic_Then_GetSubscribers_returns_all_subscribers_that_are_subscribed_to_any_of_given_topics()
        {
            const string firstPublisher = "1";
            const string anotherPublisher = "2";

            var topics = new[] { new Topic("test1"), new Topic("test2") };

            When(service =>
            {

                service.Subscribe(firstPublisher, topics);
                service.Subscribe(anotherPublisher, new[] { new Topic("test1"), new Topic("test2"), new Topic("test3") });
            });

            Then(service =>
            {

                var subscribers = service.GetSubscribers().WhoFollowAnyOfTopics(new[] { new Topic("test3") });
                var multitagSubscribers = service.GetSubscribers().WhoFollowAnyOfTopics(topics);
                Assert.That(subscribers.First().Id, Is.EqualTo(anotherPublisher));
                Assert.That(multitagSubscribers.Count(), Is.EqualTo(2));
            });
        }

        [Test]
        public void Given_test_updates_Then_GetUpdates_for_subscriber_updates_are_returned_in_valid_order()
        {
            Given(service =>
            {


                using (var fakeTime = FakeTimeScope.Begin())
                {
                    fakeTime.Now = new DateTime(2000, 1, 1);
                    service.PublishPublicUpdate(new Post("test"), "user1", "source", new Topic("publisher1"));
                    fakeTime.Now = new DateTime(2000, 1, 2);
                    service.PublishPublicUpdate(new Post("test2"), "user1", "source", new Topic("publisher1"));

                    service.Subscribe("user2", new Topic[] { new Topic("publisher1") });
                }
            });

            Then(service =>
            {

                var subscriber = service.GetSubscriber("user2");
                var updates = service.GetUpdates().ForSubscriber(subscriber).ToList();
                Assert.That(((Post)updates[0].Content).Text, Is.EqualTo("test2"));
            });
        }

        [Test]
        public void Given_non_existing_subscriber_When_event_is_published_Then_GetEvents_for_subscriber_returns_empty()
        {
            When(service =>
            {

                service.PublishPublicUpdate(new DummyUpdateContent(), "1234", "source");
            });

            Then(service =>
            {

                Assert.That(service.GetUpdates().ForSubscriber(new Subscriber("nonExistingSubscriber")), Is.Empty);
            });
        }

        [Test]
        public void Given_list_of_updates_When_GetUpdates_for_subscriber_is_called_Then_sorted_list_is_returned()
        {
            var topic = Topic.FromUserId("john");
            var firstContent = new DummyUpdateContent("1");
            var secondContent = new DummyUpdateContent("2");

            When(service =>
            {

                using (var fakeTime = FakeTimeScope.Begin())
                {
                    fakeTime.Now = new DateTime(2000, 1, 1);
                    service.PublishPublicUpdate(firstContent, "1234", "source", topic);
                    fakeTime.Now = new DateTime(2000, 1, 2);
                    service.PublishPublicUpdate(secondContent, "1234", "source", topic);
                }
                service.Subscribe("sam", new[] { topic });
            });

            Then(service =>
            {

                var subscriber = service.GetSubscriber("sam");
                var updates = service.GetUpdates();

                var update = service.GetUpdates().ForSubscriber(subscriber).First();
                Assert.That(update.Content, Is.EqualTo(secondContent));
            });
        }


        [Test]
        public void Given_list_of_updates_When_GetUpdates_for_topic_is_called_Then_sorted_list_is_returned()
        {
            var topic = Topic.FromUserId("john");
            var firstContent = new DummyUpdateContent("1");
            var secondContent = new DummyUpdateContent("2");
            When(service =>
            {

                using (var fakeTime = FakeTimeScope.Begin())
                {
                    fakeTime.Now = new DateTime(2000, 1, 1);
                    service.PublishPublicUpdate(firstContent, "1234", "source", topic);
                    fakeTime.Now = new DateTime(2000, 1, 2);
                    service.PublishPublicUpdate(secondContent, "1234", "source", topic);
                }
            });

            Then(service =>
            {

                var update = service.GetUpdates().FilterByTopics(topic).ToList().First();
                Assert.That(update.Content, Is.EqualTo(secondContent));
            });
        }

        // ***** ***** ***** ***** ***** 
        // *****  
        // *****  L I K E S
        // *****  
        // ***** ***** ***** ***** ***** 
        [Test]
        public void When_user_AddLike_Then_like_is_added_to_likes_list()
        {
            Update update = null;
            Given(service =>
            {

                update = service.PublishPublicUpdate(new DummyUpdateContent(), "1234", "source");
            });


            When(service =>
            {

                update = service.GetUpdate(update.Id);
                update.AddLike("user222");
            });

            Then(service =>
            {

                update = service.GetUpdate(update.Id);
                Assert.That(update.GetLikes().First().UserId, Is.EqualTo("user222"));
            });
        }

        [Test]
        public void When_user_AddLike_Then_likes_list_stays_distinct()
        {
            Update update = null;
            Given(service =>
            {

                update = service.PublishPublicUpdate(new DummyUpdateContent(), "1234", "source");
            });


            When(service =>
            {

                update = service.GetUpdate(update.Id);

                update.AddLike("user222");
                update.AddLike("user222");
            });

            Then(service =>
            {

                update = service.GetUpdate(update.Id);
                Assert.That(update.GetLikes().Count(), Is.EqualTo(1));
            });
        }

        [Test]
        public void Adding_a_comment_assigns_id_to_comment()
        {
            var update = DummyUpdate.From("aGuy");
            var comment = update.AddComment("EloMelo", "aGuy");

            Assert.That(comment.Id, Is.Not.Empty);
        }

        [Test]
        public void Given_update_when_user_add_comment_update_GetUpdate_returns_given_update_with_posted_comment()
        {
            const string updatePublisher = "1";
            const string user = "user";

            Update update = null;

            Given(service =>
                {
                    update = service.PublishPublicUpdate(new Post("lorem impsum"), updatePublisher, "web");
                });

            When(service =>
                {
                    update = service.GetUpdate(update.Id);
                    update.AddComment("great article man!", user);
                });

            Then(service =>
                {
                    update = service.GetUpdate(update.Id);
                    var updateComments = update.Comments.ToList();
                    Assert.That(updateComments.Count, Is.EqualTo(1));
                    Assert.That(updateComments[0].PublishedBy, Is.EqualTo(user));
                });
        }

        [Test]
        public void When_likes_was_added_to_update_Then_GetUpdates_returns_likes_sorted_by_date_descending()
        {
            Update update = null;

            When(service =>
              {

                  update = service.PublishPublicUpdate(new Post("dadasd"), "11111", "web");

                  using (var fakeTime = FakeTimeScope.Begin())
                  {
                      fakeTime.Now = new DateTime(2000, 1, 1);
                      update.AddLike("user1");
                      fakeTime.Now = new DateTime(2000, 1, 2);
                      update.AddLike("user2");
                      fakeTime.Now = new DateTime(2000, 1, 3);
                      update.AddLike("user3");
                  }
              });

            Then(service =>
            {

                var likes = service.GetUpdate(update.Id).GetLikes().ToList();
                Assert.That(likes[0].UserId, Is.EqualTo("user3"));
                Assert.That(likes[1].UserId, Is.EqualTo("user2"));
                Assert.That(likes[2].UserId, Is.EqualTo("user1"));
            });
        }

        [Test]
        public void Given_updates_when_RemoveUpdatesOlderThan_is_called_for_specific_date_Then_no_update_is_returned_for_specified_time_range()
        {
            const string publisherId = "1";
            var publishDate = new DateTime(1999, 1, 1);

            Given(service =>
                {
                    using (var fakeTimeScope = FakeTimeScope.Begin(publishDate))
                    {
                        service.PublishPublicUpdate(new Post("first post"), publisherId, "web", new Topic("topic1"));
                        fakeTimeScope.Forward(TimeSpan.FromDays(7));
                        service.PublishPublicUpdate(new Post("first post"), publisherId, "web", new Topic("topic2"));
                    }                    
                });
            When(service =>
                {
                    service.RemoveUpdatesOlderThan(DateTime.Now);
                });
            Then(service =>
                {
                    var updates = service.GetUpdates();
                    Assert.IsEmpty(updates);
                });
        }

        // ***** ***** ***** ***** ***** 
        // *****  
        // *****  T O P I C S   F I L T E R
        // *****  
        // ***** ***** ***** ***** ***** 
        [Test]
        public void Given_topics_When_GetUpdates_with_FilterByTopics_is_called_Then_updates_for_specific_topics_only_are_returned()
        {
            var topicOther = Topic.FromUserId("sam");
            var topic = Topic.FromUserId("john");
            var topic2 = Topic.FromUserId("john2");

            var expectedContent = new DummyUpdateContent("2");

            this.When(service =>
            {

                using (var fakeTime = FakeTimeScope.Begin())
                {
                    fakeTime.Now = new DateTime(2012, 01, 01);
                    service.PublishPublicUpdate(new DummyUpdateContent("1"), "1234", "source", topicOther);
                    fakeTime.Now = new DateTime(2012, 01, 02);
                    service.PublishPublicUpdate(expectedContent, "1234", "source", topic);
                    fakeTime.Now = new DateTime(2012, 01, 03);
                    service.PublishPublicUpdate(new DummyUpdateContent("1"), "1234", "source", topic2);
                }
            });

            this.Then(service =>
            {

                var updates = service.GetUpdates().FilterByTopics(topic, topic2).ToList();
                Assert.That(updates.Count, Is.EqualTo(2));
                Assert.That(updates[1].Content, Is.EqualTo(expectedContent));
            });
        }
    }
}

#endif
